package club.calong.sanniu.config;

import club.calong.sanniu.config.filter.AuthenticationFilter;
import club.calong.sanniu.http.response.Response;
import club.calong.sanniu.pojo.User;
import club.calong.sanniu.service.SysUserDetailsServiceImpl;
import club.calong.sanniu.util.CacheUtils;
import club.calong.sanniu.util.JwtTokenUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)    // 开启全局方法级别验证
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	private final SysUserDetailsServiceImpl service;

	public final JwtTokenUtil tokenUtil;

	@Autowired
	public WebSecurityConfig(SysUserDetailsServiceImpl service, JwtTokenUtil tokenUtil) {

		this.service = service;
		this.tokenUtil = tokenUtil;
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(service);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http.headers().frameOptions().disable()
				.and()
				.cors()
				.and().csrf()
				.disable()
				// 登录配置
				// 登出配置
				.logout()
				.logoutUrl("/admin/logout")
				.logoutSuccessHandler((request, response, auth) -> {
					response.setContentType("application/json;charset=utf-8");
					PrintWriter pw = response.getWriter();

					String token = request.getHeader("X-Token");
					if (token != null) {
						CacheUtils.getCache().remove(token);
					}

					Map<String, Object> map = new LinkedHashMap<>();
					map.put("code", Response.SUCCESS);
					map.put("message", Response.Message.LOGOUT_OK);

					pw.write(new ObjectMapper().writeValueAsString(map));
					pw.flush();
					pw.close();
				})
				// 配置拦截规则
				.and()
				.authorizeRequests()
				.antMatchers("/video", "/user", "/user/**", "/image", "/res/**").permitAll()
				.anyRequest().authenticated()
				// 异常处理器
				.and()
				.exceptionHandling()
				.accessDeniedHandler((request, response, exception) -> {
					// 设置数据类型
					response.setContentType("application/json;charset=utf-8");
					// 封装数据
					Map<String, Object> data = new LinkedHashMap<>(4);
					data.put("code", 6043);
					data.put("msg", "您没有访问权限");
					// 拿到响应流
					PrintWriter writer = response.getWriter();
					// 创建类型转换器
					ObjectMapper objectMapper = new ObjectMapper();
					String s = objectMapper.writeValueAsString(data);
					// 将数据写进去
					writer.write(s);
					writer.flush();
					writer.close();
				});

		http.addFilterAt(authenticationFilter(), UsernamePasswordAuthenticationFilter.class);
	}

	@Bean
	public UsernamePasswordAuthenticationFilter authenticationFilter () throws Exception {

		AuthenticationFilter filter = new AuthenticationFilter();
		filter.setFilterProcessesUrl("/admin/login");
		filter.setAuthenticationManager(authenticationManagerBean());
		filter.setAuthenticationSuccessHandler((request, response, authentication) -> {

			response.setContentType("application/json;charset=utf-8");
			PrintWriter pw = response.getWriter();

			Map<String, Object> map = new LinkedHashMap<>();

			User principal = (User) authentication.getPrincipal();

			String token = tokenUtil.createToken(principal);

			CacheUtils.getCache().put(token, principal.getUsername());

			map.put("token", token);
			map.put("data", principal);
			map.put("auth",authentication);
			pw.write(new ObjectMapper().writeValueAsString(new Response(Response.SUCCESS, Response.Message.LOGIN_OK, map)));
			pw.flush();
			pw.close();
		});
		filter.setAuthenticationFailureHandler((request, response, exception) -> {
			response.setContentType("application/json;charset=utf-8");
			PrintWriter pw = response.getWriter();

			Response result = new Response();

			result.setCode(401);
			if (exception instanceof LockedException) {
				result.setMessage("账户被锁定，登录失败！");
			} else if (exception instanceof BadCredentialsException) {
				result.setMessage("账户或者密码错误，登录失败！");
			} else if (exception instanceof DisabledException) {
				result.setMessage("账户被禁用，登录失败！");
			} else if (exception instanceof AccountExpiredException) {
				result.setMessage("账户已过期，登录失败！");
			} else if (exception instanceof CredentialsExpiredException) {
				result.setMessage("密码已过期，登录失败！");
			} else {
				result.setMessage("登陆失败！");
			}
			result.setData(null);
			pw.write(new ObjectMapper().writeValueAsString(result));
			pw.flush();
			pw.close();
		});
		return filter;
	}

	@Bean // share AuthenticationManager for web and oauth
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	@Bean
	CorsConfigurationSource corsConfigurationSource(){
		return httpServletRequest -> {
			CorsConfiguration cfg = new CorsConfiguration();
			cfg.addAllowedHeader("*");
			cfg.addAllowedMethod("*");
			cfg.addAllowedOriginPattern("*");
			cfg.setAllowCredentials(true);
			return cfg;
		};
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}
